from channels.generic.websocket import WebsocketConsumer
from channels.exceptions import StopConsumer
from asgiref.sync import async_to_sync
import json
import threading
import time
from app01 import models
from utils.repo import GitRepository


def create_node(task_object,task_id):
    '''
    创建的节点
    :return:
    '''
    # 创建节点 并返回给前端

    #数据库有节点 不要创建
    db_node_object_list=models.Node.objects.filter(task_id=task_id)
    if db_node_object_list:
        return db_node_object_list

    #数据库没有节点 不要创建
    node_object_list = []
    # 查找对应发布任务id的node
    node_queryset = models.Node.objects.filter(task_id=task_id)
    if not node_queryset:
        # 如果没有对应的node  创建
        start_node = models.Node.objects.create(text='开始', task_id=task_id, )
        node_object_list.append(start_node)
        # 判断第一个钩子处是否自定义了
        if task_object.before_download_script:
            start_node = models.Node.objects.create(text='下载前', task_id=task_id, parent=start_node)
            node_object_list.append(start_node)

        download_node = models.Node.objects.create(text='下载', task_id=task_id, parent=start_node)
        node_object_list.append(download_node)
        # 判断一二个钩子是否自定义了
        if task_object.after_download_script:
            download_node = models.Node.objects.create(text='下载后', task_id=task_id, parent=download_node)
            node_object_list.append(download_node)

        upload_node = models.Node.objects.create(text='上传', task_id=task_id, parent=download_node)
        node_object_list.append(upload_node)
        task_obj = models.DeployTask.objects.filter(pk=task_id).first()

        for server_obj in task_obj.project.server.all():
            server_node = models.Node.objects.create(text=server_obj.hostname, task_id=task_id, parent=upload_node,
                                                     server=server_obj)
            node_object_list.append(server_node)
            # 发布前的钩子位置
            if task_object.before_deploy_script:
                server_node = models.Node.objects.create(text='发布前',
                                                         task_id=task_id,
                                                         parent=server_node,
                                                         server=server_obj)
                node_object_list.append(server_node)
            deploy_node = models.Node.objects.create(text='发布', task_id=task_id, parent=server_node, server=server_obj)
            node_object_list.append(deploy_node)
            # 发布后的钩子位置
            if task_object.after_deploy_script:
                after_deploy_node = models.Node.objects.create(text='发布后', task_id=task_id, parent=deploy_node,
                                                               server=server_obj)
                node_object_list.append(after_deploy_node)
    else:
        node_object_list = node_queryset

    return node_object_list

def convert_object_to_gojs(node_object_list):
    '''
    将对象列表转换为gojs识别的json字符串
    :param node_object_list:
    :return:
    '''
    node_list = []
    for node_object in node_object_list:
        temp = {
            'key': str(node_object.id),
            'text': node_object.text,
            'color':node_object.status,
        }
        if node_object.parent:
            temp['parent'] = str(node_object.parent_id)
        node_list.append(temp)
    response_dict = {'code': 'init', 'data': node_list}
    return response_dict

class PublishConsumer(WebsocketConsumer):

    def deploy(self,task_object,task_id):
        '''
        代码的发布操作
        :return:
        '''
        #   第一步 找到数据库中的开始节点 给他变个颜色 同时 给前端返回
        start_node = models.Node.objects.filter(text='开始', task_id=task_id).first()
        start_node.status = 'green'
        start_node.save()
        async_to_sync(self.channel_layer.group_send)(
            str(task_id), {'type': 'my.send', 'message': {'code': 'update','node_id': start_node.pk,'color': 'green'}}
        )
        #从settings中 获取 代码的存放目录
        import os
        from django.conf import settings
        code_base_path=settings.DEPLOY_CODE_PATH
        #项目名字
        project_name=task_object.project.title
        #uid
        uid=task_object.uid
        # 脚本的存放目录
        script_folder=os.path.join(code_base_path,project_name,uid,'scripts')
        project_folder = os.path.join(code_base_path, project_name, uid, project_name)
        if not os.path.exists(script_folder):
            os.makedirs(script_folder)
        if not os.path.exists(project_folder):
            os.makedirs(project_folder)
        #   第二步 处理下载前的脚本
        if task_object.before_download_script:
            # 真正去执行钩子脚本
            #todo  在发布代码的服务器上执行操作
            #1  将钩子的内容写入到本地文件中
            #2  在本地执行这个脚本文件
            #3  执行成功 变成绿色 执行失败 变成红色
            #
            status='green'
            try:
                script_name='before_download_script.py'
                script_path=os.path.join(script_folder,script_name)
                with open(script_path,'w',encoding='utf-8') as f:
                    f.write(task_object.before_download_script)
                import subprocess
                subprocess.check_output('python {}'.format(script_name),shell=True,cwd=script_folder)
            except Exception as e:
                status='red'

            before_download_node = models.Node.objects.filter(text='下载前', task_id=task_id).first()
            before_download_node.status = status
            before_download_node.save()
            async_to_sync(self.channel_layer.group_send)(
                str(task_id),
                {'type': 'my.send', 'message': {'code': 'update', 'node_id': before_download_node.pk, 'color': status}}
            )

            if status=='red':
                return
        #第三步 下载
        #todo git 下载代码
        #1 获取仓库的地址    task_obj.project.repo
        #2 去仓库地址下载代码 git clone -b v1 https://gitee.com/wupeiqi/xxoo.git 下载的是v1版本
        #pip3 install gitpython
        status='green'
        try:
            GitRepository(project_folder,task_object.project.repo,task_object.tag)
        except Exception as e:
            status='red'
        # time.sleep(2)
        download_node = models.Node.objects.filter(text='下载', task_id=task_id).first()
        download_node.status = status
        download_node.save()
        async_to_sync(self.channel_layer.group_send)(
            str(task_id),
            {'type': 'my.send', 'message': {'code': 'update', 'node_id': download_node.pk, 'color': status}}
        )
        if status=='red':
            return
        #第四步 下载后
        #1 判断是否有下载后的脚本需要执行
        if task_object.after_download_script:
            # 真正去执行钩子脚本
            # 执行成功 变成绿色
            # 执行失败 变成红色
            after_download_node = models.Node.objects.filter(text='下载后', task_id=task_id).first()
            after_download_node.status = 'green'
            after_download_node.save()
            async_to_sync(self.channel_layer.group_send)(
                str(task_id),
                {'type': 'my.send', 'message': {'code': 'update', 'node_id': after_download_node.pk, 'color': 'green'}}
            )
        #   第五步 上传代码到服务器
        #   这一步不用有todo  list
        #   如果上传成功 服务器的颜色变绿
        #   如果上传失败 服务器的颜色变红
        upload_node = models.Node.objects.filter(text='上传', task_id=task_id).first()
        upload_node.status = 'green'
        upload_node.save()
        async_to_sync(self.channel_layer.group_send)(
            str(task_id),
            {'type': 'my.send', 'message': {'code': 'update', 'node_id': upload_node.pk, 'color': 'green'}}
        )
        #   第六步 连接每台服务器
        #   循环任务要上传的所有机器
        for server_obj in task_object.project.server.all():
            # 第 6.1步
            # todo 通过paramiko将代码上传到每个任务服务器上
            # 如果上传成功  节点颜色变成绿色
            # 如果上传不成功 节点颜色变成红色
            time.sleep(2)
            server_node = models.Node.objects.filter(text=server_obj.hostname, task_id=task_id,
                                                     server=server_obj).first()
            server_node.status = 'green'
            server_node.save()
            async_to_sync(self.channel_layer.group_send)(
                str(task_id),
                {'type': 'my.send', 'message': {'code': 'update', 'node_id': server_node.pk, 'color': 'green'}}
            )
            # 第6.2步
            # todo 处理发布前的钩子
            if task_object.before_deploy_script:
                before_deploy_node = models.Node.objects.filter(text='发布前', task_id=task_id,
                                                                server=server_obj).first()
                before_deploy_node.status = 'green'
                before_deploy_node.save()
                async_to_sync(self.channel_layer.group_send)(
                    str(task_id),
                    {'type': 'my.send',
                     'message': {'code': 'update', 'node_id': before_deploy_node.pk, 'color': 'green'}}
                )
            # 第6.3步
            # todo 处理发布 的操作
            deploy_node = models.Node.objects.filter(text='发布', task_id=task_id,
                                                     server=server_obj).first()
            deploy_node.status = 'green'
            deploy_node.save()
            async_to_sync(self.channel_layer.group_send)(
                str(task_id),
                {'type': 'my.send',
                 'message': {'code': 'update', 'node_id': deploy_node.pk, 'color': 'green'}}
            )
            # 第6.4步
            # todo 处理发布后的钩子
            if task_object.after_deploy_script:
                after_deploy_node = models.Node.objects.filter(text='发布后', task_id=task_id,
                                                               server=server_obj).first()
                after_deploy_node.status = 'green'
                after_deploy_node.save()
                async_to_sync(self.channel_layer.group_send)(
                    str(task_id),
                    {'type': 'my.send',
                     'message': {'code': 'update', 'node_id': after_deploy_node.pk, 'color': 'green'}}
                )

    def websocket_connect(self, message):
        #服务端接受连接 向客户端发送一个加密字符串
        task_id=self.scope['url_route']['kwargs'].get('pk')
        self.accept()
        #channel_name 为每一个连接的客户 随机生成一个字符串
        #'9999' 群聊名字
        async_to_sync(self.channel_layer.group_add)(str(task_id),self.channel_name)
        #   当用户打开页面时候
        #   查看数据库中是否有node
        # 有的话 直接返回给前端  展示 数据
        db_node_object_list=models.Node.objects.filter(task_id=task_id)
        #如果数据库有节点 应返回给前端
        if db_node_object_list:
            # node_list = []
            # for node_object in node_queryset:
            #     temp = {
            #         'key': str(node_object.id),
            #         'text': node_object.text,
            #
            #     }
            #     if node_object.parent:
            #         temp['parent'] = str(node_object.parent_id)
            #     node_list.append(temp)
            # response_dict = {'code': 'init', 'data': node_list}
            response_dict=convert_object_to_gojs(db_node_object_list)
            self.send(text_data=json.dumps(response_dict))
    def websocket_receive(self, message):

        task_id = self.scope['url_route']['kwargs'].get('pk')
        #获取任务发布对象
        task_object=models.DeployTask.objects.filter(pk=task_id).first()
        #客户端向服务端发送消息  这个方法触发
        txt=message['text']

        #Node表的初始化
        if txt=='init':
            # nodeDataArray = [
            # {"key": "start", "text": '开始', "figure": 'Ellipse', },
            # {"key": "download", "parent": 'start', "text": '下载代码',  },
            # {"key": "compile", "parent": 'download', "text": '本地编译',},
            # {"key": "zip", "parent": 'compile', "text": '打包',},
            # {"key": "c1", "text": '服务器1', "parent": "zip"},
            # {"key": "c11", "text": '服务重启', "parent": "c1"},
            # {"key": "c2", "text": '服务器2', "parent": "zip"},
            # {"key": "c21", "text": '服务重启', "parent": "c2"},
            # {"key": "c3", "text": '服务器3', "parent": "zip"},
            # {"key": "c31", "text": '服务重启', "parent": "c3"}
            # ]

            #创建节点 并返回给前端
            # node_object_list=[]
            # #查找对应发布任务id的node
            # node_queryset=models.Node.objects.filter(task_id=task_id)
            # if not node_queryset:
            #     #如果没有对应的node  创建
            #     start_node=models.Node.objects.create(text='开始',task_id=task_id,)
            #     node_object_list.append(start_node)
            #     #判断第一个钩子处是否自定义了
            #     if task_object.before_download_script:
            #         start_node=models.Node.objects.create(text='下载前',task_id=task_id,parent=start_node)
            #         node_object_list.append(start_node)
            #
            #     download_node = models.Node.objects.create(text='下载', task_id=task_id, parent=start_node)
            #     node_object_list.append(download_node)
            #     #判断一二个钩子是否自定义了
            #     if task_object.after_download_script:
            #         download_node=models.Node.objects.create(text='下载后',task_id=task_id,parent=download_node)
            #         node_object_list.append(download_node)
            #
            #     upload_node=models.Node.objects.create(text='上传',task_id=task_id,parent=download_node)
            #     node_object_list.append(upload_node)
            #     task_obj=models.DeployTask.objects.filter(pk=task_id).first()
            #
            #     for server_obj  in task_obj.project.server.all():
            #         server_node=models.Node.objects.create(text=server_obj.hostname,task_id=task_id,parent=upload_node,server=server_obj)
            #         node_object_list.append(server_node)
            #         #发布前的钩子位置
            #         if task_object.before_deploy_script:
            #             server_node = models.Node.objects.create(text='发布前',
            #                                                      task_id=task_id,
            #                                                      parent=server_node,
            #                                                      server=server_obj)
            #             node_object_list.append(server_node)
            #         deploy_node = models.Node.objects.create(text='发布', task_id=task_id,parent=server_node, server=server_obj)
            #         node_object_list.append(deploy_node)
            #         #发布后的钩子位置
            #         if task_object.after_deploy_script:
            #             after_deploy_node = models.Node.objects.create(text='发布后', task_id=task_id, parent=deploy_node,
            #                                                      server=server_obj)
            #             node_object_list.append(after_deploy_node)
            # else:
            #     node_object_list=node_queryset

            # 单发消息
            # self.send(text_data=json.dumps(response_dict))

            #第一步 没有创建过 去数据库创建所有节点 有的话 直接读取
            node_object_list=create_node(task_object,task_id)
            #第二步 生成json字符串  返回用户
            # node_list=[]
            # for node_object in node_object_list:
            #     temp={
            #         'key':str(node_object.id),
            #         'text':node_object.text,
            #     }
            #     if node_object.parent:
            #         temp['parent']=str(node_object.parent_id)
            #     node_list.append(temp)
            # response_dict={'code':'init','data':node_list}
            response_dict=convert_object_to_gojs(node_object_list)
            #群发消息 将数据通过websocket 发给前端
            async_to_sync(self.channel_layer.group_send)(str(task_id),{'type':'my.send','message':response_dict})

        # 前端发送部署请求
        if txt=='deploy':
            # #   第一步 找到数据库中的开始节点 给他变个颜色 同时 给前端返回
            # start_node=models.Node.objects.filter(text='开始',task_id=task_id).first()
            # start_node.status='green'
            # start_node.save()
            # async_to_sync(self.channel_layer.group_send)(str(task_id), {'type': 'my.send', 'message':{'code':'update','node_id':start_node.pk,'color':'green'}})
            # #   第二步 处理下载前的脚本
            # if task_object.before_download_script:
            #     # 真正去执行钩子脚本
            #     # 执行成功 变成绿色
            #     # 执行失败 变成红色
            #     before_download_node=models.Node.objects.filter(text='下载前',task_id=task_id).first()
            #     before_download_node.status='green'
            #     before_download_node.save()
            #     async_to_sync(self.channel_layer.group_send)(
            #         str(task_id),
            #         {'type': 'my.send','message': {'code': 'update','node_id': before_download_node.pk,'color': 'green'}}
            #     )
            # #   第三步 下载
            # #   去git 下载代码
            # download_node = models.Node.objects.filter(text='下载', task_id=task_id).first()
            # download_node.status = 'green'
            # download_node.save()
            # async_to_sync(self.channel_layer.group_send)(
            #     str(task_id),
            #     {'type': 'my.send', 'message': {'code': 'update', 'node_id': download_node.pk, 'color': 'green'}}
            # )
            # #   第四步 下载后
            # #
            # if task_object.after_download_script:
            #     # 真正去执行钩子脚本
            #     # 执行成功 变成绿色
            #     # 执行失败 变成红色
            #     after_download_node=models.Node.objects.filter(text='下载后',task_id=task_id).first()
            #     after_download_node.status='green'
            #     after_download_node.save()
            #     async_to_sync(self.channel_layer.group_send)(
            #         str(task_id),
            #         {'type': 'my.send','message': {'code': 'update','node_id': after_download_node.pk,'color': 'green'}}
            #     )
            # #   第五步 上传代码到服务器
            # #   这一步不用有todo  list
            # #   如果上传成功 服务器的颜色变绿
            # #   如果上传失败 服务器的颜色变红
            # upload_node = models.Node.objects.filter(text='上传', task_id=task_id).first()
            # upload_node.status = 'green'
            # upload_node.save()
            # async_to_sync(self.channel_layer.group_send)(
            #     str(task_id),
            #     {'type': 'my.send', 'message': {'code': 'update', 'node_id': upload_node.pk, 'color': 'green'}}
            # )
            # #   第六步 连接每台服务器
            # #   循环任务要上传的所有机器
            # for server_obj in task_object.project.server.all():
            #     #第 6.1步
            #     #todo 通过paramiko将代码上传到每个任务服务器上
            #     #如果上传成功  节点颜色变成绿色
            #     #如果上传不成功 节点颜色变成红色
            #     server_node = models.Node.objects.filter(text=server_obj.hostname, task_id=task_id,server=server_obj).first()
            #     server_node.status = 'green'
            #     server_node.save()
            #     async_to_sync(self.channel_layer.group_send)(
            #         str(task_id),
            #         {'type': 'my.send', 'message': {'code': 'update', 'node_id': server_node.pk, 'color': 'green'}}
            #     )
            #     #第6.2步
            #     #todo 处理发布前的钩子
            #     if task_object.before_deploy_script:
            #         before_deploy_node = models.Node.objects.filter(text='发布前', task_id=task_id,
            #                                                  server=server_obj).first()
            #         before_deploy_node.status = 'green'
            #         before_deploy_node.save()
            #         async_to_sync(self.channel_layer.group_send)(
            #             str(task_id),
            #             {'type': 'my.send', 'message': {'code': 'update', 'node_id': before_deploy_node.pk, 'color': 'green'}}
            #         )
            #     #第6.3步
            #     #todo 处理发布 的操作
            #     deploy_node = models.Node.objects.filter(text='发布', task_id=task_id,
            #                                                    server=server_obj).first()
            #     deploy_node.status = 'green'
            #     deploy_node.save()
            #     async_to_sync(self.channel_layer.group_send)(
            #         str(task_id),
            #         {'type': 'my.send',
            #          'message': {'code': 'update', 'node_id': deploy_node.pk, 'color': 'green'}}
            #     )
            #     #第6.4步
            #     #todo 处理发布后的钩子
            #     if task_object.after_deploy_script:
            #         after_deploy_node = models.Node.objects.filter(text='发布后', task_id=task_id,
            #                                                  server=server_obj).first()
            #         after_deploy_node.status = 'green'
            #         after_deploy_node.save()
            #         async_to_sync(self.channel_layer.group_send)(
            #             str(task_id),
            #             {'type': 'my.send', 'message': {'code': 'update', 'node_id': after_deploy_node.pk, 'color': 'green'}}
            #         )
            #  通过 另起一个线程的方式 去执行 代码的部署操作
            # self.deploy(task_object,task_id)
            #   channels 的小别扭  需要另起一个线程 去执行 去结果队列里去取结果 然后返回给前端
            thread=threading.Thread(target=self.deploy,args=(task_object,task_id))
            thread.start()

    def my_send(self,event):
        '''
        发送消息的函数
        :return:
        '''
        message=event['message']    # response_dict
        self.send(json.dumps(message))
    def websocket_disconnect(self, message):
        #客户端断开连接 此方法触发
        task_id = self.scope['url_route']['kwargs'].get('pk')
        async_to_sync(self.channel_layer.group_discard)(str(task_id), self.channel_name)
        print('客户端断开连接')
        raise StopConsumer()